package com.lancer.cloud.auth.config;

import com.lancer.cloud.auth.handler.CustomWebResponseExceptionTranslator;
import com.lancer.cloud.auth.security.UserDetailsImpl;
import com.lancer.cloud.common.constants.MqQueueConstants;
import com.lancer.cloud.common.constants.SecurityConstants;
import com.lancer.cloud.common.constants.ServiceNameConstants;
import com.lancer.cloud.common.constants.UserConstants;
import com.lancer.cloud.common.dto.SysLogDto;
import com.lancer.cloud.common.enums.OperationStatusEnum;
import com.lancer.cloud.common.enums.SysLogTypeEnum;
import com.lancer.cloud.common.util.IpUtils;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * OAuth2认证配置
 *
 * @author caijinbang
 * @date 2019-06-15 17:25
 */
@Slf4j
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {


    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private CustomWebResponseExceptionTranslator customWebResponseExceptionTranslator;

    @Autowired
    private DataSource dataSource;

    /**
     * 配置token存储到redis中
     *
     * @return
     */
    @Bean
    RedisTokenStore redisTokenStore(){
        return new RedisTokenStore(redisConnectionFactory);
    }

    /**
     *   默认值InMemoryTokenStore对于单个服务器是完全正常的（即在发生故障的情况下，低流量和热备份备份服务器）。
     *   大多数项目可以从这里开始，也可以在开发模式下运行，以便轻松启动没有依赖关系的服务器。
     *   这JdbcTokenStore是同一件事的JDBC版本，它将令牌数据存储在关系数据库中。
     *   如果您可以在服务器之间共享数据库，则可以使用JDBC版本，
     *   如果只有一个，则扩展同一服务器的实例，或者如果有多个组件，则授权和资源服务器。
     *   要使用JdbcTokenStore你需要“spring-jdbc”的类路径。
     *
     * @return
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        ////这个地方指的是从jdbc查出数据来存储
        clients.withClientDetails(clientDetails());
    }

    @Bean
    public ClientDetailsService clientDetails() {
        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        clientDetailsService.setSelectClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT_BY_CLIENT_ID);
        clientDetailsService.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT);
        return clientDetailsService;
    }




    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients();

    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // token增强链
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        // 把jwt增强，与额外信息增强加入到增强链
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter()));
        endpoints.tokenStore(redisTokenStore())
            .tokenEnhancer(tokenEnhancerChain)
            .reuseRefreshTokens(false)
            .authenticationManager(authenticationManager);
        // 添加认证异常处理器
        endpoints.exceptionTranslator(customWebResponseExceptionTranslator);

    }


    /**
     * jwt token增强，添加额外信息
     * @return
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return (oAuth2AccessToken, oAuth2Authentication) -> {

            // 添加额外信息的map
            final Map<String, Object> additionMessage = new HashMap<>(2);
            // 获取当前登录的用户
            UserDetailsImpl user = (UserDetailsImpl) oAuth2Authentication.getUserAuthentication().getPrincipal();
            // 登录日志记录
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            SysLogDto sysLogDto = buildSysLogDto(user, request);
            rabbitTemplate.convertAndSend(MqQueueConstants.SYS_LOG_QUEUE, sysLogDto);
            log.info("当前用户为：{}", user);
            // 如果用户不为空 则把id放入jwt token中
            if (user != null) {
                additionMessage.put(UserConstants.USER_ID, user.getUserId());
                additionMessage.put(UserConstants.USER_NAME, user.getUsername());
            }
            ((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(additionMessage);
            return oAuth2AccessToken;
        };
    }

    /**
     * 设置签名
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setSigningKey(SecurityConstants.SIGN_KEY);
        return jwtAccessTokenConverter;
    }

    /**
     * 构建日志信息
     * @param user
     * @param request
     * @return
     */
    private SysLogDto buildSysLogDto(UserDetailsImpl user, HttpServletRequest request){
        SysLogDto sysLogDto = new SysLogDto();
        sysLogDto
            .setCreateBy(user.getUsername())
            .setRequestUri(request.getRequestURI())
            .setUserAgent(request.getHeader("user-agent"))
            .setType(SysLogTypeEnum.LOGIN.getCode().toString())
            .setStatus(OperationStatusEnum.SUCCESS.getCode().toString())
            .setModuleName("auth认证模块")
            .setActionName("登录")
            .setServiceId(ServiceNameConstants.LANCER_AUTH)
            .setRemoteAddr(IpUtils.getIpAddrByRequest(request))
            .setMethod(request.getMethod());
        return sysLogDto;
    }
}
